// writable: false
let Person1 = {};
Object.defineProperty(Person1,'name',{
  value: 'jack',
  writable: false // 是否可以改变,默认false
})
Person1.name='rose';
console.log('Person1.name', Person1.name); // jack

// writable: true
let Person2 = {};
Object.defineProperty(Person2, 'name', {
   value: 'jack',
   writable: true, // 是否可以改变
})
Person2.name='rose'
console.log('Person2.name',Person2.name);// rose
/* 
  注意，如果描述符中的某些属性呗省略，会使用以下默认规则
  属性名                      默认值
  value                      undefined
  get                        undefined
  set                        undefined
  writable                   false
  enumerable                 false
  configurable               false
*/

// get: 一个给属性提供getter的方法，如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。
// set: 一个给属性提供setter的方法，如果没有setter则为undefined。该方法将接受唯一参数，并将该参数的新值分配给该属性。默认值为undefined
let Person3 = {};
let temp = 'jack';
Object.defineProperty(Person3,'name',{
  // value: 'jack',// 如果这边写上value 则 报错
  get:function(){
    return temp;
  },
  set:function(val) {
    temp=val;
  }
});
console.log('Person3.name', Person3.name); // jack 这边调用了 get, 初始值
Person3.name='rose'// 这边调用了 set 
console.log('Person3.name', Person3.name); // rose 这边调用了 get

/* 数据描述符和存取描述均具有以下描述符
  1、configurable 描述属性是否配置，以及可否删除
  2、enumerable 描述属性是否会出现在for in 或者 Object.keys()的遍历中 
*/
// 1、configurable
// 不能删除属性值
let Person4 ={};
Object.defineProperty(Person4,'name',{
  value: 'jack',
  configurable: false,// false 不可删除
  writable: true, // true 可修改
  enumerable: true // 出现在 for in 或者 Object.keys 中
})
delete Person4.name; 
console.log('Person4.name',Person4.name );// jack, 并未删除成功

'use strict'
// 可删除属性
let Person5 ={};
Object.defineProperty(Person5,'name',{
  value: 'jack',
  configurable: true, // false 不可删除
  writable: true,
  enumerable: true
})
delete Person5.name; 
console.log('Person5.name',Person5.name ); //   undefined, 删除成功

// 不能重新定义属性
let Person6 ={};
Object.defineProperty(Person6,'name',{
  value: 'jack',
  configurable: false, // 重新定义直接报错
})
Object.defineProperty(Person6,'name',{
  value: 'rose',
}) //  Cannot redefine property: name
console.log('Person6.name', Person6.name);

let Person7 = {};
Object.defineProperty(Person7,'name',{
  value: 'jack',
  configurable: true,
  writable: true, 
})
Object.defineProperty(Person7,'name',{
  value: 'rose',
})
console.log('Person7.name', Person7.name); // rose

let Person8 = {};
Object.defineProperty(Person8,'name',{
  value: 'jack',
  configurable: true,
  writable: false, 
})
Object.defineProperty(Person8,'name',{
  value: 'rose',
})
// 通过属性定义的形式可以修改name的属性值
console.log('Person8.name', Person8.name); // rose


let Person9 = {};
Object.defineProperty(Person9,'name',{
  value: 'jack',
  configurable: false,
  writable: true, 
})
Object.defineProperty(Person9,'name',{
  value: 'rose',
})
// 通过属性定义的形式可以修改name的属性值
console.log('Person9.name', Person9.name); // rose

let Person10 = {};
Object.defineProperty(Person10,'name',{
  value: 'jack',
  configurable: true,
  writable: false, 
})
Object.defineProperty(Person10,'name',{
  value: 'rose',
})
// 通过属性定义的形式可以修改name的属性值
console.log('Person10.name', Person10.name); // rose
// 通过赋值的形式不可以修改，因为 writable: false
Person10.name='tom'
console.log('Person10.name', Person10.name);//  rose


let Person11 = {};
Object.defineProperty(Person11,'name',{
  value: 'jack',
  configurable: false,
  writable: true, 
})
Object.defineProperty(Person11,'name',{
  value: 'rose',
})
// 通过属性定义的形式可以修改name的属性值
console.log('Person11.name', Person11.name); // rose
// 通过赋值的形式可以修改，因为 writable: true
Person11.name='tom'
console.log('Person11.name', Person11.name);//  tom
/* 
  configurable: false 时，不能删除当前属性，且不能重新配置当前属性的描述符(有一个小小的意外：可以把writable的状态由true改为false,但是无法由false改为true),但是在writable: true的情况下，可以改变value的值
  configurable: true时，可以删除当前属性，可以配置当前属性所有描述符。
*/

// 2、enumerable
let Person12= {};
Object.defineProperty(Person12,'name',{
  value: 'jack',
  enumerable:false
})
Person12.gender = 'male';
Object.defineProperty(Person12,'age',{
  value: '26',
  enumerable: true
})
console.log(Object.keys(Person12)); // [ 'gender', 'age' ], 无 name 属性
for (let k in Person12 ) {console.log(k);}// gender, age, 无 name 属性
/* 
  propertyIsEnumerable 表示指定的属性名是否可枚举，返回 布尔值。
  每个对象都有一个 propertyIsEnumerable 方法。
  此方法可以确定对象中指定的属性是否可以被 for...in 循环枚举，但是通过原型链继承的属性除外。
  如果对象没有指定的属性，则此方法返回 false
*/
console.log(Person12.propertyIsEnumerable('name'));// false
console.log(Person12.propertyIsEnumerable('gender'));// true
console.log(Person12.propertyIsEnumerable('age'));// true

  // 注意一下两种区别
let Person13 = {};
Person13.gender = 'male';
// 等价于
Object.defineProperty(Person13,'gender',{
  value: 'male',
  configurable: true,
  writable: true,
  enumerable: true
})
Object.defineProperty(Person13, 'age',{
  value: '26'
})
// 等价于
Object.defineProperty(Person13,'age',{
  value: '26',
  configurable: false,
  writable:false,
  enumerable:false
})


// 不变性
// 1、对象常量
// 结合writable: false 和 configurable: false 就可以创建一个真正的常量属性（不可修改，不可重新定义或者删除
let Person14 = {};
Object.defineProperty(Person14,'name',{
  value: 'jack',
  writable: false,
  configurable: false
})
delete Person14.name;// 不可删除
Person14.name='rose';// 不可重新赋值
console.log('Person14.name: ', Person14.name);// jack
// 通过赋值，还可以添加新属性
Person14.gender = 'male';
console.log('Person14.gender: ', Person14.gender);// male
//不可重新定义 ，运行直接报错 TypeError: Cannot redefine property: name，因为  configurable: false
Object.defineProperty(Person14,'name',{
  value: 'rose'
})

//2、 禁止扩展
// 如果你想禁止一个对象添加新属性并且保留已有属性，就可以使用 Object.preventExtensions(...)
'use strict'
let Person15 = {
  name: 'jack'
};
Object.preventExtensions(Person15); // 禁止扩展
console.log('Person15.name', Person15.name);// jack
// 在非严格模式下，创建属性gender会静默失败，在严格模式下，将会抛出异常。
Person15.gender = 'male';
console.log('Person15.gender', Person15.gender);//undefined
let Person16 = {
  name: 'jack'
};
Object.preventExtensions(Person16); // 禁止扩展
// 任然可以进行配置
Object.defineProperty(Person16,'name',{
  value: 'rose',
  writable: false,
  configurable: false
})
// 输出配置的结果
console.log('Person16.name', Person16.name);// rose
// 不能进行扩展,在非严格模式下，创建属性gender会静默失败，在严格模式下，将会抛出异常。
Person16.gender = 'male';
console.log('Person16.gender', Person16.gender);// undefined

//3、密封
/* 
  Object.seal()会创建一个密封的对象，
  这个方法实际上会在一个现有对象上调用object.preventExtensions(...)
  并把所有现有属性标记为configurable:false。
  密封之后不仅不能添加新属性，也不能重新配置或者删除任何现有属性（虽然可以改属性的值）
*/ 
let Person17 = {
  name: 'jack'
};
Object.seal(Person17);
Person17.gender= 'male';
// 不能扩展
console.log('Person17.gender', Person17.gender);// undefined
// 再次验证
console.log('Object.keys(Person17)', Object.keys(Person17));// [ 'name' ]
// 不能再次配置属性, 运行报错
Object.defineProperty(Person17,'name', {
  name:'rose',
  configurable: true
})
console.log('Person17.name', Person17.name); // TypeError: Cannot redefine property: name

//  4、冻结
/* 
  Object.freeze()会创建一个冻结对象，
  这个方法实际上会在一个现有对象上调用Object.seal(),
  并把所有现有属性标记为writable: false,这样就无法修改它们的值。
  这个方法是你可以应用在对象上级别最高的不可变性，它会禁止对于对象本身及其任意直接属性的修改（但是这个对象引用的其他对象是不受影响的）
你可以深度冻结一个对象，具体方法为，首先这个对象上调用Object.freeze()然后遍历它引用的所有对象，并在这些对象上调用Object.freeze()。但是一定要小心，因为这么做有可能会无意中冻结其他共享对象。
*/
let Person18 = {
  name: 'jack'
};
Object.freeze(Person18);
Person18.gender= 'male';
// 不能扩展属性
console.log('Person18.gender', Person18.gender); // undefined
// 再次验证
console.log('Object.keys(Person18)', Object.keys(Person18));// [ 'name' ]
Person18.name ='tom';
// 不可修改已有属性值
console.log('Person18.name', Person18.name);// jack
// 不能再次配置属性, 运行报错
Object.defineProperty(Person18,'name', {
  name:'rose',
  configurable: true
})
console.log('Person18.name', Person18.name); // TypeError: Cannot redefine property: name

// 属性定义和属性赋值
// 1、属性定义,通过Object.defineProperty()形式
/* 
1、如果Obj没有名为Prop的自身属性的话：如果Obj是可扩展的话，则创建Prop这个自身属性，否则拒绝
2、如果Obj已经有了名为Prop的自身属性：则按照下面的步骤重新配置这个属性
3、如果这个已有的属性是不可配置的，则进行下面的操作会被拒绝

1: 将一个数据属性转换成访问器属性，反之变然
2: 改变`[[Configurable]]`或`[[Enumerable]]`
3: 改变[[Writable]]由false变为true
4: 在`[[Writable]]`为`false`时改变`[[Value]]`
5: 改变[[Get]]或[[Set]]

4、否则这个已有的属性可以被重新配置

*/
/* 
属性赋值,通过obj.prop = ''prop"形式
1、如果在原型链上存在一个名为P的只读属性（只读的数据属性或者没有setter的访问器属性），则拒绝
2、如果在原型链上存在一个名为P的且拥有setter的访问器属性，则调用这个setter
3、如果没有名为P的自身属性，则如果这个对象是可扩展的，就创建一个新属性，否则，如果这个对象是不可扩展的，则拒绝
4、如果已经存在一个可写的名为P的自身属性，则调用Object.defineProperty(),该操作只会更改P属性的值，其他的特性（比如可枚举性）都不会改变
*/


/* 
作用以及影响
属性的定义操作和赋值操作各自有自己的作用和影响。
赋值可能会调用原型上的setter，定义会创建一个自身属性。
原型链中的同名只读属性可能会阻止赋值操作，但不会阻止定义操作。
如果原型链中存在一个同名的只读属性，则无法通过赋值的方式在原对象上添加这个自身属性，必须使用定义操作才可以。
这项限制是在ECMAScript 5.1中引入的
*/
'use strict'
let proto={
  get bar() {
    console.log('Getter');
    return 'a'
  }
}
let obj = Object.create(proto);
obj.bar = 'hello' // 直接操作会失败
console.log('obj.bar', obj.bar); // a
// 通过 定义操作
Object.defineProperty(obj,'bar',{
  value: 'hello'
})
console.log('obj.bar', obj.bar); // hello
console.log('proto.bar', proto.bar);// a

'use strict'
let proto1 = {};
Object.defineProperty(proto1,'foo', {
  // foo的属性值
  value: 'a',
  // foo的属性特性
  writable: false, // 只读
  configurable: true // 可配置
});
let obj1 = Object.create(proto1);// a
console.log('obj1: ', obj1);// {};
// 修改失败
obj1.foo = 'hello';
console.log('obj1.foo', obj1.foo); // a
// 通过定义形式，创建了自身属性
Object.defineProperty(obj1,'foo',{
  value: 'b'
})
console.log('obj1.foo', obj1.foo); // b
console.log('proto1.foo', proto1.foo); // a
// 修改失败
obj1.foo='hello';
console.log('obj1.foo', obj1.foo); // b

/* 
  赋值运算符不会改变原型链上的属性
  不能通过为obj.foo赋值来改变proto.foo的值。
  这种操作只会在obj上新建一个自身属性
*/
let proto2 ={
  foo:'a'
}
let obj2 = Object.create(proto2);
console.log('obj2: ', obj2);// {}
obj2.foo = 'b';
console.log('obj2.foo', obj2.foo); // b
console.log('proto2.foo', proto2.foo); // a
console.log(obj2.hasOwnProperty('foo'));// true


// 对象字面量中的属性是通过定义操作添加的。
let obj3 = {
  name: 'jack'
}
// 等同于
let obj3 = new Object();
Object.defineProperty(obj3,{
  name: 'jack',
  enumerable: true,
  configurable: true,
  enumerable: true
})

let obj4 = {};
obj4.name = 'jack';
// 等同于
let obj4 = {};
Object.defineProperty(obj4,'name',{
  value: 'jack',
  writable: true,
  enumerable: true,
  configurable: true
})

let obj5 = {};
Object.defineProperty(obj5,'name', {
  value: 'jack'
})
// 等同于
Object.defineProperty(obj5,'name',{
  value: 'jack',
  enumerable: false,
  configurable: false,
  writable: false
})
// 通过  Object.defineProperty 方法 监听对象中属性的变化；
const obj= {};
let _name = '张三';
Object.defineProperty(obj,'name', {
  get:function() {
    console.log('get', _name); // 监听
    return _name;
  },
  set:function(newValue) {
    console.log('set', newValue); // 监听
    _name = newValue;
  }
});
// 设置属性，调用了 set方法，
obj.name='李四'; 
// 设置属性，调用了 get方法，
obj.name
// 不管是属性的取值还是设置值都能监听到属性值的变化，并且还能执行自定义监听代码函数
// 模拟实现vue响应式
var myVm = {};
var data = {
  name:'zhangsan',
  age: 26
};
for (key in data) {
  console.log('key: ', key);
  (function(key){
    Object.defineProperty(myVm, key,{
      get :function() {
        console.log('我是监听函数get',data[key]);
        return data[key]
      },
      set:function(newVale){
        console.log('我是监听函数set',newVale);
        data[key]=newVale;
      }
    })
  })(key);
};